package com.momo.hr.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.momo.hr.bean.Hr;
import com.momo.hr.bean.RespBean;
import com.momo.hr.service.HrService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


/**
 * 为SpringSecurity的配置类
 */

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //注入自己写的UserService
    @Autowired
    HrService hrService;

    //注入路径过滤器拦截器
    @Autowired
    CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;

    //注入权限决策管理器
    @Autowired
    CustomMyDecisionManager customMyDecisionManager;

    //自定义过滤器注入（这里为验证码的自定义过滤器）
    @Autowired
    VerificationCodeFilter verificationCodeFilter;



    // 虽然这两个都是继承WebSecurityConfigurerAdapter后重写的方法，
    // 但是http.permitAll不会绕开springsecurity的过滤器验证，
    // 相当于只是允许该路径通过过滤器，而web.ignoring是直接绕开spring security的所有filter，
    // 直接跳过验证。
    @Override
    public void configure(WebSecurity web) throws Exception {
        //如果访问Login的话 就不需要经过拦截器
        web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode");
    }

    //注入加密方式
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


    //配置用户来源：为数据库
    //配置加密。解密方式BCrypt
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(hrService).passwordEncoder(passwordEncoder());
    }

    //配置SpringSecurity方式
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //在用户验证过滤器前加一个自定义的验证码过滤器
        http.addFilterBefore(verificationCodeFilter, UsernamePasswordAuthenticationFilter.class);
        http.authorizeRequests()
                /*.anyRequest()
                .authenticated()*///任何请求都需要认证才可以访问
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);//设置一个路径资源过滤器
                        o.setAccessDecisionManager(customMyDecisionManager);//设置到一个决策处理器
                        return o;
                    }
                })
                .and()
                .formLogin()
                .usernameParameter("username")//设置请求参数名称
                .passwordParameter("password")
                .loginProcessingUrl("/doLogin")
                //登陆成功的回掉函数
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=UTF-8");//设置返回的格式，因为是前后端分离,所以使用json格式
                        PrintWriter writer = httpServletResponse.getWriter();
                        //从SpringSecurity中获取当前登录的用户
                        Hr hr = (Hr)authentication.getPrincipal();//拿到登陆成功的对象(需要进行强制类型转换)
                        hr.setPassword(null);//不能传递给前端字符串的明文密码
                        RespBean ok = RespBean.ok("登录成功", hr);
                        String responseJson = new ObjectMapper().writeValueAsString(ok);//jackSon转换成JSON格式
                        writer.write(responseJson);//写出数据
                        writer.flush();//刷出内容
                        writer.close();//关闭I/O
                    }
                })
                //登陆失败的回掉函数
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=UTF-8");//设置返回的格式，因为是前后端分离,所以使用json格式
                        PrintWriter writer = httpServletResponse.getWriter();
                        //通过异常来判断账户没有登录失败的问题
                        RespBean respBean = RespBean.error("系统异常，请重新登录");;
                        if(e instanceof LockedException){//账户被锁定
                            respBean = RespBean.error("账户被锁定，联系管理员");
                         } else if (e instanceof AccountExpiredException){//账户过期
                            respBean = RespBean.error("账户过期，联系管理员");
                        } else if (e instanceof CredentialsExpiredException){
                            respBean = RespBean.error("密码过去，联系管理员");
                        } else if(e instanceof DisabledException){//账户被禁用
                            respBean = RespBean.error("账户被Ban，联系管理员");
                        } else if(e instanceof BadCredentialsException){
                            respBean = RespBean.error("用户或者密码错误，请重新输入");
                        }
                            String responseJson = new ObjectMapper().writeValueAsString(respBean);
                            writer.write(responseJson);
                            writer.flush();
                            writer.close();
                    }
                })
                .permitAll()//表示以上所有的资源都被允许访问
                .and()
                .logout()//设置登出以后的
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=UTF-8");
                        PrintWriter writer = httpServletResponse.getWriter();
                        writer.write(new ObjectMapper().writeValueAsString(RespBean.ok("注销成功")));
                        writer.flush();
                        writer.close();
                    }
                })
                .permitAll()//登出的一些接口 或者页面不要被拦截
                .and()
                .csrf().disable().exceptionHandling()
                //如果发生异常那么就进行以下处理
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
                        httpServletResponse.setContentType("application/json;charset=UTF-8");
                        httpServletResponse.setStatus(401);//设置后端响应吗为 401 那么前端通过此响应码来处理异常
                        PrintWriter writer = httpServletResponse.getWriter();
                        RespBean respBean = RespBean.error("访问失败");
                        //如果这个异常属于 未登录异常 如果 应为SpringSecurity默认给没有登录用户也设置了角色
                        //所以我们这里抛出的是 “InsufficientAuthenticationException”
                        // 的意思:
                        if(e instanceof InsufficientAuthenticationException){
                            respBean.setMsg("请求失败");
                        }
                        writer.write(new ObjectMapper().writeValueAsString(respBean));
                        writer.flush();
                        writer.close();
                    }
                });
    }
}

